/**
 * DRepImpl.java
 *
 * @author Alex Tough (07-41604)
 * @author Carlos Colmenares (06-39380)
 *
 * Implementación de una interfaz remota de un DRep, el cuál
 * es un manejador sencillo de repositorios.
 */

import nanoxml.*;
import java.lang.IllegalStateException;
import java.util.Enumeration;
import java.io.File;
import java.io.FileReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.net.MalformedURLException;

public class DRepImpl extends UnicastRemoteObject implements DRep {

    static final long serialVersionUID = 1234123412345L;
    /**
     * El puerto al cuál estará asociado el DRep, este puerto
     * realmente nunca es abierto o usado dado el uso de RMI,
     * es sólo un identificador.
     */
    private int puertoDRep;

    /**
     * Nombre del host en donde se encuentra el proceso DMon
     */
    private String hostDMon;

    /**
     * Puerto por el cuál el proceso DMon escucha, nuevamente,
     * este número es sólo un identificador, el puerto nunca es
     * abierto.
     */
    private int puertoDMon;

    /**
     * El nombre del directorio que servirá como repositorio.
     */
    private String directorioDRep;

    /**
     * El nombre o identificador único de este DRep.
     */
    private String nombreDRep;

    /**
     * La ruta hacia el archivo de configuración XML del DRep.
     */
    private String archivoConf;

    /**
     * En caso de ser verdadera, el servidor imprimirá información
     * extra acerca de la ejecución.
     */
    private boolean verboso;

    /**
     * La referencia al directorio repositorio
     */
    private File repositorio;

    /**
     * La referencia remota a la interfaz DMon a la
     * cuál este DRep está conectado
     */
    private DMon dmon;

    /**
     * Este es el nombre de identificación único del DRep
     */
    private String drepID;

    /**
     * Construye una clase DRep con los argumentos dados
     * @param args Los argumentos que definen el comportamiento
     *             y características del DRep
     */
    public DRepImpl(String[] args) throws RemoteException {
        super();
        parsearArgumentos(args);
    }


    /**
     * Esta función imprime los flags que se deben proporcionar
     * al programa para su correto funcionamiento
     */
    private void imprimirUso(){
        String mnsjError =
            "uso: drep [-l <puerto local clientes>] [-h "+
            "<host de dmon>] [-p <puerto dmon>] [-d <rdirectorio>] "+
            "[-n <nombre>] [-c <archivo de configuración>] [-v]";
        System.out.println(mnsjError);
        System.exit(1);
    }

    /**
     * Establece los valores por defecto de los parametros nombre,
     * ip_drep, puerto, ldirectorio y archivo contenidos en formato
     * XML en el archivo archivoConf
     * 
     * @throws IOException Cuando hay un error interno al cerrar
     *                     archivo_conf
     *
    */
    private void establecerValoresDefecto()
			throws IOException{

	try{
	    if(!this.archivoConf.endsWith(".xml")){
		System.out.println("El archivo "+this.archivoConf+" no es un archivo xml. Sera ignorada.");
		return;
	    } 
	    XMLElement info_conf = new XMLElement();
	    FileReader fr = new FileReader(this.archivoConf);
	    info_conf.parseFromReader(fr);
	    fr.close();	
	    Enumeration children = info_conf.enumerateChildren();
	    while(children.hasMoreElements()){
		XMLElement child = new XMLElement();
		child = (XMLElement)children.nextElement();
		if(child.getName().equals("nombre")){
		    this.nombreDRep = child.getContent().trim();
		}
		else if(child.getName().equals("hostserv")){
		    this.hostDMon = child.getContent().trim();
		}
		else if(child.getName().equals("puertoserv")){
		    this.puertoDMon = Integer.parseInt(child.getContent().trim());
		}
		else if(child.getName().equals("puertocli")){
		    this.puertoDRep = Integer.parseInt(child.getContent().trim());
		}
		else if(child.getName().equals("directorio")){
		    this.directorioDRep = child.getContent().trim();
		}
	    }
	}
	catch(FileNotFoundException fnfe){
	    System.out.println("El archivo " + this.archivoConf + " no existe");
	}
	catch(XMLParseException xmlpe){
	    System.out.println("Error al hacer parsing del archivo "+this.archivoConf+".Sera ignorada");
	}		
    }
    
    /**
     * Parsea los argumentos de entrada y asigna valores por default a
     * ellos. También valida la entrada, es decir, se chequea si los
     * valores dados son correctos.
     *
     * @param args Los argumentos del programa.
     */
    private void parsearArgumentos(String[] args){
        try {
            //Valores por default
            this.puertoDRep = 2000;
            this.hostDMon = "localhost";
            this.puertoDMon = 2100;
            this.nombreDRep = "drep";
            this.archivoConf = null;
            this.verboso = false;
            this.directorioDRep = ".";

            int i = 0;
            while( i < args.length ){

                if( args[i].equals("-l") && i+1<args.length ){
                    this.puertoDRep = Integer.parseInt(args[++i].trim());
                }
                else if( args[i].equals("-h") && i+1<args.length ){
                    this.hostDMon = args[++i];
                }
                else if( args[i].equals("-p") && i+1<args.length ){
                    this.puertoDMon = Integer.parseInt(args[++i]);
                }
                else if( args[i].equals("-d") && i+1<args.length ){
                    this.directorioDRep = args[++i];
                }
                else if( args[i].equals("-n") && i+1<args.length ){
                    this.nombreDRep = args[++i];
                }
                else if( args[i].equals("-c") && i+1<args.length ){
                    this.archivoConf = args[++i];
		    try{
			establecerValoresDefecto();		
		    } 
		    catch(IOException ioex){
			System.out.println("Error al cerrar el archivo "+this.archivoConf+".Abortar");
		    }
                }
                else if( args[i].equals("-v") ){
                    this.verboso = true;
                }
                else { //( args[i].equals("--help") )
                    imprimirUso();
                }
                i++;
            }
            if( puertoDRep <= 0 || puertoDRep > 65535 ||
                puertoDMon <= 0 || puertoDMon > 65535 ){
                System.out.println("Error, ingrese números de "+
                                    "puerto válidos");
                System.exit(-1);
            }

            //Se obtiene el número de identificación único
            //del drep
            String drepHost =
                java.net.InetAddress.getLocalHost().getHostName();

            this.drepID = this.nombreDRep+"@"+drepHost+":"+this.puertoDRep;

            //Conectar al DMon
            this.dmon = (DMon) 
                Naming.lookup("rmi://"+this.hostDMon+":"+
                        this.puertoDMon+"/dmon");

            this.dmon.registrarDRep(this.drepID);
        }
        catch(DMonException e){
            System.err.println("Error con el DMon");
            System.err.println(e);
            System.exit(1);
        }
        catch(NumberFormatException nfe){
            System.err.println("Error obteniendo número de puertos, por "+
                    "favor indique sólo números enteros");
            System.exit(1);
        }
        catch(RemoteException e){
            System.err.println("Error de conexión RMI");
            e.printStackTrace();
            System.exit(1);
        }
        catch(MalformedURLException e){
            System.err.println("Error de conexión");
            e.printStackTrace();
            System.exit(1);
        }
        catch(NotBoundException e){
            System.err.println("No existe un DMon en esa dirección");
            e.printStackTrace();
            System.exit(1);
        }
        catch(java.net.UnknownHostException e){
            System.err.println("Error obteniendo hostname");
            e.printStackTrace();
            System.exit(1);
        }

        //Tratar de abrir el directorio en donde se operará,
        //de no existir, crearlo.
        this.repositorio = new File(this.directorioDRep);
        if(!this.repositorio.isDirectory()){
            if(this.verboso){
                System.out.println("Creando el directorio repositorio: "
                        +this.directorioDRep);
            }
            this.repositorio.mkdir();
        }
        if(!this.repositorio.canRead()){
            System.err.println("Error, no se puede leer el directorio "
                    +"repositorio: "+this.directorioDRep);
            System.exit(1);
        }
    }

    public boolean isVerboso(){
        return this.verboso;
    }

    public File getRepositorio(){
        return this.repositorio;
    }

    public int getPuertoDRep(){
        return this.puertoDRep;
    }

    /**
     * Método para desconectar el DRep del DMon
     */
    public void desconectarDRep() throws RemoteException, DMonException {
        this.dmon.desconectarDRep(this.drepID);
    }

    //......................................................................
    //      Implementaciones de la interfaz DRep
    //......................................................................

    public String[] ldir() throws RemoteException {
        
        File[] archivos = repositorio.listFiles();
        String[] listaDeArchivos = new String[archivos.length];
        for(int i=0;i<archivos.length;i++){
            listaDeArchivos[i] = archivos[i].getName() + "\t" +
                    archivos[i].length()+"B";
        }
        return listaDeArchivos;
    }
    
    public String[] tuser() throws RemoteException {
        return this.dmon.tuser();
    }
    
    public void sube(String nombreDeArchivo, byte[] archivo)
        throws RemoteException{
       
        try {	   
	    FileOutputStream fos = new FileOutputStream(this.repositorio.getName()+"/"+nombreDeArchivo);
	    fos.write(archivo);
	    fos.close(); 

            //Reportar al dmon
            this.dmon.subir();

        } catch (IOException e) {
            System.err.println("Error leyendo/escribiendo");
            System.err.println(e);
            throw new RemoteException("Error interno de Entrada/Salida");
        }
    }

    public byte[] baja(String nombreDeArchivo)
            throws DRepException, RemoteException {

        try {
            File archivo = new File(this.repositorio.getName()+"/"
                    +nombreDeArchivo);

            if(!archivo.exists() || !archivo.isFile()){
                throw new DRepException("Archivo no existente");
            }

            //Si el archivo es muy grande, abortar
            if( archivo.length() > Integer.MAX_VALUE/2 ){
                throw new DRepException("Archivo muy grande! "
                        +nombreDeArchivo);
            }

            byte[] streamDeBytes = new byte[(int)archivo.length()];

            FileInputStream fis = new FileInputStream(archivo);
            fis.read(streamDeBytes, 0, (int)archivo.length());

            fis.close();

            this.dmon.bajar();

            return streamDeBytes;

        } catch(IOException e) {

            System.err.println("Error leyendo/escribiendo");
            System.err.println(e);
            throw new RemoteException("Error interno de Entrada/Salida");

        }
    }

    public int nrep() throws RemoteException {
        return this.dmon.nrep();
    }

    public void registrarUsuario(String nombreDeUsuario)
        throws RemoteException, DMonException
    {
        this.dmon.registrarDCLI(nombreDeUsuario,this.drepID);
    }

    public void desconectarDCLI(String nombreDeUsuario)
        throws RemoteException, DMonException
    {
        this.dmon.desconectarDCLI(nombreDeUsuario, this.drepID);
    }

}
